/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年9月19日
 */
package com.massyframework.beanskin.license.signature.wizard;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.massyframework.beanskin.license.License;
import com.massyframework.beanskin.license.signature.ras.BCRSAKeyReader;
import com.massyframework.beanskin.license.signature.ras.BCRSAService;
import com.massyframework.beanskin.service.crypto.RSAKeyReader;
import com.massyframework.beanskin.service.crypto.RSAService;
import com.massyframework.beanskin.util.IOUtils;

import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;

/**
 * 签名结果
 * @author huangkh
 *
 */
public class SignatureOutputController extends Controller {
	
	private static final String LICENSE         = "license";
	private static final String GROUPID         = "groupId";
	private static final String MODULEID        = "moduleId";
	private static final String VERSIONRANGE    = "version-range";
	private static final String CUSTOMER        = "customer";
	private static final String ID              = "id";
	private static final String NAME            = "name";
	private static final String EXPIRE_DATE     = "expire-date";
	private static final String SIGNATURE       = "signature";
	private static final String CERTIFICATE     = "x509cert";
	
	private Document document;
	
	@FXML
	private Button btnPrevious;
	@FXML
	private TextArea txtOutput;

	/**
	 * 
	 */
	public SignatureOutputController() {
		
	}

	@Override
	protected void init() {

	}

	@Override
	public void destroy() {

	}

	@Override
	public void beforeShow() {
		this.document = null;
		this.txtOutput.clear();
		
		List<License> licenses = new ArrayList<>();
		List<Module> modules = 
				this.getWizard().getSignatureData().getSignaModules();
		CertificateEntry entry =
				this.getWizard().getSignatureData().getCertificateEntry();
		
		try {
			RSAService service = new BCRSAService();
			RSAKeyReader reader = new BCRSAKeyReader();
			
			//执行签名
			for (Module module: modules) {
				License.Builder builder = this.createLicenseBuilder(module);
				builder.customerId(this.getWizard().getSignatureData().getCustomerId());
				
				this.doSign(builder, entry, service, reader);
				licenses.add(builder.build());
			}
			
			this.document = this.createDocument(licenses);
			ByteArrayOutputStream os=new ByteArrayOutputStream();  
			try {
				this.saveTo(this.document, os);
				this.txtOutput.appendText(os.toString());
			}finally {
				IOUtils.close(os);
			}		
		}catch(Exception e) {
			e.printStackTrace();
			
			Alert alert = new Alert(AlertType.ERROR);
			alert.setTitle("错误");
			alert.setHeaderText("签名时发生非预期错误");
			alert.setContentText(e.getMessage());
			alert.showAndWait();
		}
	}
	
	/**
	 * 保存xml文档
	 * @param doc
	 * @param os
	 * @throws TransformerException
	 * @throws IOException
	 */
	protected void saveTo(Document doc, OutputStream os) throws TransformerException, IOException {
		URL url = this.getClass().getClassLoader().getResource("license/format.xml");
		StreamSource streamSource = null;
		InputStream is = null;
		if (url != null) {
			is = url.openStream();
			streamSource = new StreamSource(is);
		}
		
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		Transformer transformer =  streamSource == null ?
				transformerFactory.newTransformer() :
					transformerFactory.newTransformer(streamSource);
		DOMSource source = new DOMSource(doc);

		try {
			StreamResult result = new StreamResult(os);
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");
			transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,"yes");
			transformer.transform(source, result);
		}finally {
			IOUtils.close(is);
		}
	}
	
		
	/**
	 * 根据许可创建xml document文件
	 * @param licenses
	 * @return
	 * @throws Exception
	 */
	protected Document createDocument(List<License> licenses) throws Exception {
		URL url = this.getClass().getClassLoader().getResource("license/template.xml");
		if (url == null) {
			throw new FileNotFoundException("未找到模板文件:license/template.xml");
		}
		
		InputStream is = null;
		try {
			is = url.openStream();
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document result = builder.parse(is);
			
			long customerId = licenses.get(0).getCustomerId();
			String x509Cert = licenses.get(0).getX509Cert();
			
			this.writeCustomer(result, customerId, null);
			for (License license: licenses) {
				Element element = result.createElement(LICENSE);
				Node node = (Node)element;
				result.getDocumentElement().appendChild(node);
				//result.appendChild(node);
				this.writeLicense(license, result, node);
				
			}
			
			this.appendX509Cert(result, x509Cert);
			return result;
		}finally {
			IOUtils.close(is);
		}
	}
	
	/**
	 * 写入客户信息
	 * @param doc xml document
	 * @param id 客户编号
	 * @param name 客户名称
	 */
	protected void writeCustomer(Document doc, long id, String name) {
		Element element = doc.createElement(CUSTOMER);
		Node node = (Node)element;
		doc.getDocumentElement().appendChild(node);
		
		element = doc.createElement(ID);
		Node child = (Node)element;
		child.setTextContent(Long.toString(id));
		node.appendChild(child);
		
		if (name != null) {
			element = doc.createElement(NAME);
			child = (Node)element;
			child.setTextContent(name);
			node.appendChild(child);
		}
	}
	
	/**
	 * 写入许可
	 * @param license 许可
	 * @param doc xml文档
	 * @param node 许可节点
	 */
	protected void writeLicense(License license, Document doc, Node node) {
		Element element = doc.createElement(GROUPID);
		Node child = (Node)element;
		child.setTextContent(license.getGroupId());
		node.appendChild(child);
		
		element = doc.createElement(MODULEID);
		child = (Node)element;
		child.setTextContent(license.getModuleId());
		node.appendChild(child);
		
		element = doc.createElement(VERSIONRANGE);
		child = (Node)element;
		child.setTextContent(license.getVersionRange().toString());
		node.appendChild(child);
		
		if (license.getExpireDate() != null) {
			element = doc.createElement(EXPIRE_DATE);
			child = (Node)element;
			child.setTextContent(license.getExpireDate().toString());
			node.appendChild(child);
		}
		
		List<String> paramNames = license.getParameterNames();
		if (!paramNames.isEmpty()) {
			element = doc.createElement("parameters");
			child = (Node)element;
			node.appendChild(child);
			
			for (String name: paramNames) {
				element = doc.createElement("parameter");
				Node paramNode = (Node)element;
				Attr attr = doc.createAttribute("name");
				attr.setTextContent(name);
				paramNode.getAttributes().setNamedItem(attr);
				attr = doc.createAttribute("value");
				attr.setTextContent(license.getParameter(name));
				paramNode.getAttributes().setNamedItem(attr);
				
				child.appendChild(paramNode);
			}
		}
		
		element = doc.createElement(SIGNATURE);
		child = (Node)element;		
		CDATASection section = doc.createCDATASection(license.getSignature());
		child.appendChild(section);
		node.appendChild(child);
	}
	
	/**
	 * 添加509证书
	 * @param doc xml节点
	 * @param x509Cert x509证书
	 */
	protected void appendX509Cert(Document doc, String x509Cert) {
		Element element = doc.createElement(CERTIFICATE);
		Node child = (Node)element;		
		CDATASection section = doc.createCDATASection(x509Cert);
		child.appendChild(section);
		doc.getDocumentElement().appendChild(child);
	}
	
	/**
	 * 签发许可
	 * @param builder 许可构建器
	 * @param entry 证书记录
	 * @param service rsa签名服务
	 * @param reader rsa密钥读取器
	 * @throws Exception 
	 */
	protected void doSign(License.Builder builder, 
			CertificateEntry entry, 
			RSAService service,
			RSAKeyReader reader) throws Exception {
		try {
			String content = builder.toContent();
			String privateKey = reader.getString(entry.getKey());
			String x509Cert   = reader.getString(entry.getX509Certificate());
			String signature = service.sign(content, privateKey);
			
			System.err.println(x509Cert);
			builder.x509cert(x509Cert);
			builder.signature(signature);
		}catch(Exception e) {
			throw e;
		}
	}
		
	
	/**
	 * 生成签名内容
	 * @param module {@link Module}
	 * @return {@link String}
	 */
	protected License.Builder createLicenseBuilder(Module module) {
		License.Builder result = License.newBuilder()
			.groupId(module.getGroupId())
			.moduleId(module.getModuleId())
			.versionRange(module.getVersionRange());
		
		if (module.getExpireDate() != null) {
			result.expireDate(module.getExpireDate());
		}
		
		List<Parameter> params = module.getParameters();
		for (Parameter param: params) {
			result.addParameter(param.getName(), param.getValue());
		}
			
		return result;
	}
	
	/**
	 * 签名
	 * @param content
	 * @param module
	 * @return
	 */
	protected String signature(String content, Module module) {
		return null;
	}
	
	@FXML
	protected void onSaveClick() {
		FileChooser fileChooser = new FileChooser();
		fileChooser.setTitle("保存许可文件");
		fileChooser.setInitialFileName("licenses.xml");
		fileChooser.getExtensionFilters().addAll(
		        new ExtensionFilter("许可文件", "*.xml"));

		File selectedFile = fileChooser.showSaveDialog(this.getWizard().getStage());
		if (selectedFile  != null) {
			FileOutputStream os = null;
			try {
				os = new FileOutputStream(selectedFile);
				this.saveTo(this.document, os);
			}catch(Exception e) {
				Alert alert = new Alert(AlertType.ERROR);
				alert.setTitle("错误");
				alert.setHeaderText("保存许可文件失败.");
				alert.setContentText(e.getMessage());
				alert.showAndWait();
			}finally {
				IOUtils.close(os);
			}
		}
	}

	@FXML
	protected void onPreviousClick() {
		this.getWizard().doPrevious();
	}
}
